﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using winS.Unity.Rendering;

namespace winS.Unity
{
    /// <summary>
    /// The runtime for Unity.
    /// </summary>
    public class Runtime
    {
        /// <summary>
        /// Initialized event.
        /// </summary>
        public static event Action onInitialized;

        /// <summary>
        /// Is runtime initialized?
        /// </summary>
        public static bool initialized { get; private set; }

        /// <summary>
        /// Render module.
        /// </summary>
        public static Render render { get; private set; } = new Render();
        /// <summary>
        /// Device module.
        /// </summary>
        public static Device device { get; private set; } = new Device();
        /// <summary>
        /// Event system Module.
        /// </summary>
        public static EventSystem eventSystem { get; private set; } = new EventSystem();

        private static readonly ModuleManager moduleManager = new ModuleManager();

        private static bool initializing;

        /// <summary>
        /// Initialize Runtime.
        /// </summary>
        public static async Task Initialize()
        {
            if (initialized || initializing) return;
            initializing = true;
            device.display.UpdateInfo();

            Type baseType = typeof(Module);
            List<InitializeInfo<Type>> moduleInfoList = new List<InitializeInfo<Type>>();
            List<InitializeInfo<MethodInfo>> methodInfoList = new List<InitializeInfo<MethodInfo>>();

            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (var type in assembly.GetTypes())
                {
                    foreach (var staticMethodInfo in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static))
                    {
                        RuntimeInitializeMethodAttribute runtimeInitializeMethod = staticMethodInfo.GetCustomAttribute<RuntimeInitializeMethodAttribute>();
                        if (runtimeInitializeMethod != null) methodInfoList.Add(new InitializeInfo<MethodInfo>(runtimeInitializeMethod.priority, staticMethodInfo));
                    }

                    if (type.IsAbstract || !type.IsSubclassOf(baseType)) continue;
                    //没有无参构造函数, 报错
                    if (type.GetConstructor(Type.EmptyTypes) == null) throw new Exception($"{nameof(Runtime)}: Module \"{type.Name}\" must have parameterless constructors!");
                    ModuleInitializationPriorityAttribute moduleInitializationPriority = type.GetCustomAttribute<ModuleInitializationPriorityAttribute>();
                    moduleInfoList.Add(new InitializeInfo<Type>(moduleInitializationPriority == null ? int.MinValue : moduleInitializationPriority.priority, type));
                }
            }
            //先初始化模块
            moduleInfoList.Sort();
            moduleManager.EnsureCapacity(moduleInfoList.Count);
            foreach (var moduleInfo in moduleInfoList)
            {
                Module module = Activator.CreateInstance(moduleInfo.obj) as Module;
                await module.Initialize();
                moduleManager.AddModule(module);
            }

            //再初始化方法
            methodInfoList.Sort();
            foreach (var methodInfo in methodInfoList)
            {
                object returnValue = methodInfo.obj.Invoke(null, null);
                if (returnValue is Task task) await task;
            }
            initialized = true;
            initializing = false;
            onInitialized?.Invoke();
        }

        /// <summary>
        /// Get module of type <typeparamref name="T"/>.
        /// </summary>
        public static T GetModule<T>() where T : Module
        {
            return moduleManager.GetModule<T>();
        }

        private readonly struct InitializeInfo<T> : IComparable<InitializeInfo<T>>
        {
            public readonly int priority;

            public readonly T obj;

            public InitializeInfo(int priority, T obj)
            {
                this.priority = priority;
                this.obj = obj;
            }
            public int CompareTo(InitializeInfo<T> other)
            {
                return priority.CompareTo(other.priority);
            }
        }
    }
}